L3: Tworzenie modeli uczenia maszynowego

Efekty kształcenia laboratorium


Podczas niniejszych zajęć:

  • dowiesz się jak przeprowadzić analizę statystyczną zbioru danych i na co zwrócić uwagę

  • poznasz podstawowe pojęcia z zakresu uczenia maszynowego

  • poznasz i przeprowadzisz cały proces uczenia masznowego - od wczytania zbioru, poprzez jego transformacje, uczenie, aż po walidację krzyżową i dostrajanie parametrów modelu

  • poznasz podstawowe mechanizmy sprawdzania jakości modelu uczenia maszynowego

Analiza statystyczna zbioru treningowego


Przed rozpoczęciem rozwiązywania problemu przy użyciu metod uczenia maszynowego, w szczególności przed rozpoczęciem budowania modelu, konieczne jest sprawdzenie, z jakimi danymi przyszło się nam mierzyć.

Wśród podstawowych kwestii, które powinniśmy sprawdzić, są:

  • ile mamy cech?

  • które spośród nich to cechy kategoryczne, a które numeryczne?

  • jakie wartości przyjmują poszczególne cechy?

  • czy wśród danych są brakujące wartości?

  • czy istnieje i jak wygląda etykieta? (w szczególności - czy mierzymy się z zadaniem klasyfikacji, regresji czy klasteryzacji?)

  • czy dane są zbalansowane względem danej wyjściowej?

Dla małych i prostych zbiorów do nauki (tzw. toy tasks), zazwyczaj wystarczające jest ręczne przejrzenie pliku z danymi, by potrafić odpowiedzieć na w/w pytania. Niemniej przy bardziej ambitnych zadaniach, z pomocą przychodzą narzędzia automatyzujące pracę.

Przykładowy zbiór danych

Przeanalizujmy klasyczny zbiór danych dotyczący wina (opublikowany przez Forina, M. et al, PARVUS - An Extendible Package for Data Exploration, Classification and Correlation. Institute of Pharmaceutical and Food Analysis and Technologies, Via Brigata Salerno, 16147 Genoa, Italy, więcej informacji tutaj)

Zbiór zawiera właściwości fizykochemiczne różnych próbek wina pobranych z jednego z regionów słonecznej Italii, jednakże pochodzących od trzech różnych plantatorów. Założeniem problemu jest określenie, który z nich jest wytwórcą danej próbki.

W celu uczynienia przykładu ambitniejszym, zbiór został celowo zaszumiony - tj. usunięto losowo część wartości.

Zacznijmy od wczytania zbioru:

import pandas as pd

dataset = pd.read_csv('../docs/lab3/wine_with_nulls.csv', )
dataset.head()
alcohol malic_acid ash alcalinity_of_ash magnesium total_phenols flavanoids nonflavanoid_phenols proanthocyanins color_intensity hue od280/od315_of_diluted_wines proline target
0 14.23 1.71 2.43 15.6 127.0 2.80 3.06 0.28 2.29 5.64 1.04 3.92 1065.0 0
1 13.20 1.78 2.14 11.2 100.0 2.65 2.76 0.26 1.28 4.38 1.05 3.40 1050.0 0
2 13.16 2.36 2.67 18.6 101.0 NaN 3.24 0.30 2.81 5.68 1.03 3.17 1185.0 0
3 14.37 1.95 2.50 16.8 113.0 3.85 NaN 0.24 2.18 7.80 0.86 3.45 1480.0 0
4 13.24 2.59 NaN 21.0 118.0 2.80 2.69 0.39 1.82 4.32 1.04 2.93 735.0 0

Na pierwszy rzut oka możemy stwierdzić, że wszystkie kolumny są numeryczne, ale ich wartości różnią się dość znacząco.

Podstawowe informacje o statystykach zbioru danych możemy uzyskać przy wbudowanej w Pandas metodzie describe()

dataset.describe()
alcohol malic_acid ash alcalinity_of_ash magnesium total_phenols flavanoids nonflavanoid_phenols proanthocyanins color_intensity hue od280/od315_of_diluted_wines proline target
count 171.000000 170.000000 170.000000 167.000000 169.000000 171.000000 167.000000 171.000000 173.000000 170.000000 175.000000 170.000000 172.000000 178.000000
mean 13.009357 2.318059 2.360000 19.404790 100.088757 2.291988 2.019760 0.365614 1.583295 5.009529 0.959920 2.615176 756.209302 0.938202
std 0.819951 1.108406 0.275004 3.328986 14.490898 0.626310 1.006122 0.123074 0.572402 2.292621 0.228525 0.706861 315.609153 0.775035
min 11.030000 0.740000 1.360000 10.600000 70.000000 0.980000 0.340000 0.130000 0.410000 1.280000 0.480000 1.270000 278.000000 0.000000
25% 12.370000 1.575000 2.202500 17.150000 88.000000 1.730000 1.095000 0.270000 1.250000 3.220000 0.785000 1.970000 508.000000 0.000000
50% 13.050000 1.850000 2.360000 19.400000 98.000000 2.350000 2.170000 0.340000 1.540000 4.640000 0.980000 2.780000 679.000000 1.000000
75% 13.700000 3.030000 2.547500 21.500000 108.000000 2.800000 2.885000 0.445000 1.950000 6.122500 1.120000 3.177500 996.250000 2.000000
max 14.830000 5.800000 3.230000 30.000000 162.000000 3.880000 5.080000 0.660000 3.580000 13.000000 1.710000 4.000000 1680.000000 2.000000

Jednakże w celu dokładniejszej analizy zbioru, posłużymy się biblioteką Pandas Profiling.

Pandas Profiling

Jest to biblioteka automatycznie analizująca zbiór danych i generujaca interaktywny raport. Alternatywnie, raport można zapisać w formacie .html

Na dzień tworzenia tego zadania (2021.02.06) wersja 2.10 biblioteki zawiera błąd nie pozwalający generować raportu w środowisku Jupyter. Posłużymy się więc wersją 2.9, która działa poprawnie.

Instalacja przebiega standardowo:

pip install pandas-profiling==2.9.0

Użycie biblioteki jest niezwykle proste:

from pandas_profiling import ProfileReport

profile = ProfileReport(dataset)
profile.to_notebook_iframe()

Z raportu dowiadujemy się między innymi:

  • mamy 13 kolumn numerycznych (dane wejściowe), jedną kategoryczną (etykieta) - będziemy więc zajmować się klasyfikacją

  • klasy są całkiem nieźle zbalansowane (39%, 33%, 27%)

  • mamy kolumny z pustymi wartościami

  • możemy dokładnie przeanalizować statystyki poszczególnych cech, ich histogramy oraz wykresy zależności pomiędzy nimi

Wprowadzenie do uczenia maszynowego


  1. Uczenie maszynowe (ang. machine learning) jest obecnie najpopularniejszą dziedziną sztucznej inteligencji.

  2. Polega ono na automatycznej budowie modelu poprzez ekspozycję algorytmu na dane treningowe w procesie zwanym uczeniem.

  3. Model uczenia maszynowego posiada zdolność rozpoznawania wzorców wykrytych w danych, dzięki czemu jest w stanie dokonywać predykcji.

  4. Celem uczenia maszynowego jest tworzenie modeli zdolnych do generalizacji, tj. poprawnego predykowania na danych nie użytych do treningu.

W zależności od problemu który ma zostać rozwiązany, stosuje się różne metody i algorytmy, w szczególności:

  • jeśli celem jest przypisanie danym pewnej kategorycznej etykiety, mówimy o klasyfikacji - np. określenie czy na zdjęciu jest kot, czy pies

  • jesli celem jest umiejscowienie danych na pewnej ciągłej skali liczbowej, mówimy o regresji - np. prognozowanie wartości produktu

  • jeśli celem jest zgrupowanie podobnych sobie danych, mówimy o klasteryzacji - np. przypisanie użytkowników Twittera do grup w zależności od poruszanych tematów

Klasyfikacja i regresja są przykładami uczenia nadzorowanego, gdzie do treningu oprócz danych wejściowych opisujących problem, musimy też posiadać etykietę, tj. klasę lub wartość która ma być wyjściem modelu.

Klasteryzacja to przykład uczenia nienadzorowanego, gdzie model realizuje swoje zadanie bez dodatkowych informacji z naszej strony.

Uczenie maszynowe przy pomocy sklearn


Scikit-learn (aka sklearn) jest aktualnie najpopularniejszą biblioteką w Pythonie pozwalającą kompleksowo przeprowadzać proces machine learningowy, który zazwyczaj składa się z następujących elementów:

  1. wczytanie zbioru danych

  2. preprocessing zbioru:

    • transformacje cech (normalizacja, skalowanie, kodowanie, dyskretyzacja, embeddowanie, ekstrakcja cech itd)

    • rozwiązanie kwestii brakujących danych

    • rozwiązanie kwestii powtarzających się danych

    • rozwiązanie kwestii imbalansu klas (oversampling/undersampling)

    • augmentacja danych

  3. selekcja cech/redukcja wymiarowości

  4. uczenie modelu

  5. dostosowywanie (fine-tuning) parametrów

Instalacja przebiega standardowo:

pip install scikit-learn

Wczytanie zbioru danych

Scikit-learn posiada wbudowany zestaw standardowych, benchmarkowych zbiorów danych - jak np. używany w przykładzie zbiór wine. Pozwala także w prosty sposób generować syntetyczne dane.

Więcej informacji o dostępnych w bibliotece zbiorach danych tutaj

# używamy wczytanego wcześniej zbioru `wine`

X = dataset.drop(columns=['target'])
y = dataset['target']

Trzymając się przyjętej konwencji matematycznej, zbiór danych wejściowych nazywamy X, natomiast wyjściowych - y

Preprocessing zbioru danych

Odpowiednie przygotowanie danych uczących jest kluczem do sukcesu każdego przedsięwzięcia machine learningu - w myśl zasady Garbage in, garbage out. Przed przystąpieniem do uczenia, zazwyczaj konieczne jest wykonanie co najmniej kilku kroków wstępnego przetwarzania danych.

Krok ten silnie uzależniony jest od samych danych - inaczej przygotowywać będziemy dane tekstowe, numeryczne, dźwiękowe czy obrazy. Dużą rolę gra tutaj także planowany do użycia algorytm - część z nich wymaga np. ustandaryzowanych danych (tj. pochodzących z rozkładu zbliżonego do normalnego, posiadających średnią w 0 i odchylenie standardowe równe 1).

Duże znaczenie dla jakości procesu ma balans klas - w przypadku danych niezbalansowanych, część algorytmów wykazuje tendencje do preferowania klasy nadreprezentowanej, przez co popełniają błędy.

Ponadto w wielu zbiorach danych występują cechy, które nie są informacyjne. Usunięcie takich cech często pozwala na poprawę jakości predykcji. Ograniczenie ilości cech ułatwia także technicznie proces uczenia - algorytm musi przetworzyc mniej danych, wiec dzieje się to szybciej. Istnieje wiele metod selekcji cech, np. bazujacych na testach statystycznych. Ich ogólna idea polega na ocenieniu cech wg. zadanej miary jakości, a następnie wyboru najbardziej wartościowych.

Alternatywnie stosuje się metody redukcji wymiarowości. W przeciwieństwie do selekcji cech, która jedynie wybiera spośród istniejących już danych pewien ich podzbiór, algorytmy redukcji wymiarowości przekształcają dane, tworząc nowe, bardziej informacyjne cechy. Najpopularniejszą metodą redukcji wymiarowości jest PCA

Hint

Preprocessingowi poświęcone zostało Laboratorium 4, gdzie w/w mechanizmy zostaną omówione dokładniej.

W naszym komfortowym przykładzie nie ma dużej potrzeby dostosowywania danych:

  • klasy są w miarę dobrze zbalansowane

  • nie ma duplikatów

  • nie ma danych kategorycznych, poza etykietą, więc nie ma potrzeby ich kodowania

Obsługa danych brakujących

W przetwarzanym zbiorze danych występują brakujące wartości. Dane w takiej postaci nie nadają się do celów uczenia maszynowego.

Istnieją dwa podejścia na rozwiązanie tej kwestii:

  • usunięcie rekordów zawierających brakujące wartości

  • uzupełnienie tych wartości

Usunięcie rekordów z brakującymi danymi

Jest to rozwiązanie najprostsze i potencjalnie najlepsze dla jakości klasyfikacji - usuwając “niepewne” rekordy, uczymy klasyfikator jedynie na pełnowartościowych danych.

Minusem tego rozwiązania jest jednakże zmniejszenie się zbioru danych - polecane jest jego stosowanie jedynie w przypadku, gdy mamy wystarczającą ilość danych

# sprawdźmy, czy faktycznie występują brakujące wartości
print(X.isna().any())
print(X.shape, '\n\n')

# usuńmy je
X_dropped = X.dropna(axis=0)

# sprawdźmy ponownie
print(X_dropped.isna().any())
print(X_dropped.shape)
alcohol                         True
malic_acid                      True
ash                             True
alcalinity_of_ash               True
magnesium                       True
total_phenols                   True
flavanoids                      True
nonflavanoid_phenols            True
proanthocyanins                 True
color_intensity                 True
hue                             True
od280/od315_of_diluted_wines    True
proline                         True
dtype: bool
(178, 13) 


alcohol                         False
malic_acid                      False
ash                             False
alcalinity_of_ash               False
magnesium                       False
total_phenols                   False
flavanoids                      False
nonflavanoid_phenols            False
proanthocyanins                 False
color_intensity                 False
hue                             False
od280/od315_of_diluted_wines    False
proline                         False
dtype: bool
(98, 13)
Uzupełnianie danych brakujących

W przypadku gdy mamy niedostateczną ilość danych i nie możemy ich usunąć bez konsekwencji, alternatywą jest uzupełnienie danych.

W tym celu należy wybrać odpowiednią wartość. Najpopularniejsze strategie to:

  • wybranie odpowiedniej statystyki (średnia/mediana) w zbiorze

  • wybranie odpowiedniej statystyki (średnia/mediana) w danej klasie

  • wybranie wartości najczęstszej w zbiorze

  • wybranie wartości najczęstszej w danej klasie

  • interpolacja wartości

Przydatne metody biblioteki Pandas: [fillna](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.fillna.html#pandas.DataFrame.fillna) [interpolate](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.interpolate.html#pandas.DataFrame.interpolate)
# uzupełnimy wartości średnimi w zbiorze

X = X.fillna(value=X.mean())
X
alcohol malic_acid ash alcalinity_of_ash magnesium total_phenols flavanoids nonflavanoid_phenols proanthocyanins color_intensity hue od280/od315_of_diluted_wines proline
0 14.23 1.71 2.43 15.6 127.0 2.800000 3.06000 0.28 2.29 5.64 1.04 3.92 1065.0
1 13.20 1.78 2.14 11.2 100.0 2.650000 2.76000 0.26 1.28 4.38 1.05 3.40 1050.0
2 13.16 2.36 2.67 18.6 101.0 2.291988 3.24000 0.30 2.81 5.68 1.03 3.17 1185.0
3 14.37 1.95 2.50 16.8 113.0 3.850000 2.01976 0.24 2.18 7.80 0.86 3.45 1480.0
4 13.24 2.59 2.36 21.0 118.0 2.800000 2.69000 0.39 1.82 4.32 1.04 2.93 735.0
... ... ... ... ... ... ... ... ... ... ... ... ... ...
173 13.71 5.65 2.45 20.5 95.0 1.680000 0.61000 0.52 1.06 7.70 0.64 1.74 740.0
174 13.40 3.91 2.48 23.0 102.0 1.800000 0.75000 0.43 1.41 7.30 0.70 1.56 750.0
175 13.27 4.28 2.26 20.0 120.0 1.590000 0.69000 0.43 1.35 10.20 0.59 1.56 835.0
176 13.17 2.59 2.37 20.0 120.0 1.650000 0.68000 0.53 1.46 9.30 0.60 1.62 840.0
177 14.13 4.10 2.36 24.5 96.0 2.050000 0.76000 0.56 1.35 9.20 0.61 1.60 560.0

178 rows × 13 columns

Skalowanie cech

Nasz dataset jest już kompletny i zawiera dane tylko i wyłącznie numeryczne, które jednakże mają skrajnie różne wartości pomiędzy cechami. Poddamy je więc standaryzacji

from sklearn.preprocessing import StandardScaler
# zdefiniujmy funkcję wyświetlajacą statystyki średniej i odchylenia standardowego
def print_dataset_stats(X):
    print('Średnia cech:\n', X.mean(axis=0))
    print()
    print('Odchylenie standardowe cech:\n', X.std(axis=0))
    print()

Important

Większość algorytmów i klas zawartych w scikit-learn implementuje interfejs fit/transform - tj. przy pomocy funkcji fit dokonywane jest dopasowywanie algorytmów do danych uczących (uczenie/ustalanie parametrów modelu), natomiast przy pomocy funkcji transform dokonywane jest przekształcenie danych.

scaler = StandardScaler().fit(X)

print("Przed standaryzacją")
print_dataset_stats(X)
X = scaler.transform(X)
print("Po standaryzacji")
print_dataset_stats(X)
Przed standaryzacją
Średnia cech:
 alcohol                          13.009357
malic_acid                        2.318059
ash                               2.360000
alcalinity_of_ash                19.404790
magnesium                       100.088757
total_phenols                     2.291988
flavanoids                        2.019760
nonflavanoid_phenols              0.365614
proanthocyanins                   1.583295
color_intensity                   5.009529
hue                               0.959920
od280/od315_of_diluted_wines      2.615176
proline                         756.209302
dtype: float64

Odchylenie standardowe cech:
 alcohol                           0.803573
malic_acid                        1.083068
ash                               0.268717
alcalinity_of_ash                 3.223883
magnesium                        14.117679
total_phenols                     0.613800
flavanoids                        0.974356
nonflavanoid_phenols              0.120615
proanthocyanins                   0.564260
color_intensity                   2.240211
hue                               0.226580
od280/od315_of_diluted_wines      0.690702
proline                         310.213727
dtype: float64

Po standaryzacji
Średnia cech:
 [-2.91402358e-15 -1.59672525e-16 -1.26615322e-15 -3.99181312e-16
  1.75639777e-15  1.99590656e-16  3.99181312e-17 -2.79426919e-16
 -5.98771968e-17  2.89406451e-16 -4.79017575e-16  4.79017575e-16
 -1.99590656e-16]

Odchylenie standardowe cech:
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]

Uczenie modelu

Mając odpowiednio przygotowane dane, możemy przystąpić do procesu uczenia. Pierwszym krokiem będzie podział zbioru na część treningową (na której nauczymy model) oraz testową (na której przetestujemy jego jakość). Taki podział pozwala zmierzyć zdolność modelu do generalizacji - sprawdzamy jakość na danych, które nie były użyte do nauki, których “model wcześniej nie widział”.

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=13)
print('X_train: ', X_train.shape)
print('X_test: ', X_test.shape)
print('y_train: ', y_train.shape)
print('y_test: ', y_test.shape)
X_train:  (142, 13)
X_test:  (36, 13)
y_train:  (142,)
y_test:  (36,)

Finalnie jesteśmy gotowi stworzyć klasyfikator. Dla przykładu użyjemy algorytmu SVM. Przykłady innych modeli można znaleźć tutaj

from sklearn.svm import SVC
classifier = SVC()
classifier.fit(X_train, y_train)
SVC()

Możemy go teraz użyć w celu predykcji nowych wartości:

classifier.predict(X_test[:2])
array([2, 0])

Important

Istnieje wiele modeli uczenia maszynowego. Wybór odpowiedniego zależy od problemu który rozwiązujemy, danych które posiadamy, pożądanemu rezultatowi i warunkom działania (np. pożądana szybkość uczenia i inferencji).

Do najpopularniejszych modeli nadzorowanych należą - drzewa decyzyjne (i lasy losowe), kNN, SVM, Naive Bayes, sieci neuronowe.

Niestety, dokładne wprowadzenie w fascynującą dziedzinę modelu uczenia maszynowego leży poza zakresem tego laboratorium.

Potoki

W powyższym przykładzie po wczytaniu danych przetworzyliśmy je przez dwa algorytmy - StandardScaler i SVM. W przypadku bardziej rozbudowanego preprocessingu, niezwykle przydatna staje się możliwość łączenia wszystkich użytych mechanizmów w jeden potok:

from sklearn.pipeline import make_pipeline
from sklearn.datasets import load_wine

X, y = load_wine(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=13)

pipeline_classifier = make_pipeline(
    StandardScaler(), 
    SVC()
)

pipeline_classifier.fit(X_train, y_train)
Pipeline(steps=[('standardscaler', StandardScaler()), ('svc', SVC())])
pipeline_classifier.predict(X_test[:2])
array([2, 0])

Analiza jakości modelu

Na tym etapie posiadamy zbiór danych podzielony na część treningową i testową, oraz klasyfikator nauczony na częsci treningowej. Jak sprawdzić, jak dobrze jest on nauczony?

Do określenia jakości modelu służy szereg metryk, różnych w zależności od problemu.

W zadaniu klasyfikacji, standardowo używa się następujących miar wywodzących się z klasyfikacji binarnej:

  • accuracy, oznaczająca procent poprawnie oznaczonych przykładów

  • F1 - będąca średnią miar precision i recall, mierzących skłonności modelu do niepopełniania błędów

from sklearn.metrics import f1_score
from sklearn.metrics import accuracy_score

y_predicted = pipeline_classifier.predict(X_test)

print('Accuracy: ', accuracy_score(y_test, y_predicted))
print('F1: ', f1_score(y_test, y_predicted, average='macro'))
Accuracy:  0.9722222222222222
F1:  0.9696394686907022

Dokładniejsze informacje o jakości klasyfikacji możemy uzyskac np. przy pomocy metody classification_report

from sklearn.metrics import classification_report

print(classification_report(y_test, y_predicted))
              precision    recall  f1-score   support

           0       1.00      1.00      1.00        12
           1       0.94      1.00      0.97        15
           2       1.00      0.89      0.94         9

    accuracy                           0.97        36
   macro avg       0.98      0.96      0.97        36
weighted avg       0.97      0.97      0.97        36

W przypadku zadania regresji, standardowo używaną miarą jest błąd średniokwadratowy (ang. mean square error). Więcej informacji o różnych rodzajach metryk jakości modelu - tutaj

Walidacja krzyżowa

Powyżej nauczyliśmy model klasyfikować - i to z bardzo dobrą jakością!

Jednakże, nauczyliśmy nasz model na pewnym podzbiorze danych i przetestowaliśmy na innym. Nie mamy pewności, czy model sam w sobie wykazuje się dobrą jakością, czy to akurat ten specyficzny wybór podzbiorów danych daje tak dobre wyniki.

Z pomocą przychodzi tutaj walidacja krzyżowa | sprawdzanie krzyżowe (ang. cross-validation). Jest to procedura polegająca na kilkukrotnym uruchomieniu procedury uczenia modelu, za każdym razem na innym podzbiorze danych. Uśredniony wynik takich kilku modeli pozwala z większą dokładnością wyrokować o jakości samego modelu.

from sklearn.model_selection import cross_validate

X, y = load_wine(return_X_y=True)
cv_results = cross_validate(
    pipeline_classifier, 
    X, 
    y,
    scoring='f1_macro'
)
cv_results['test_score'].mean()
0.983504493924021

Dostosowywanie parametrów

Praktycznie każdy z algorytmów uczenia maszynowego posiada pewne parametry (lub hiper-parametry), które wpływają na proces uczenia. W przypadku użytego algorytmu SVM są to np. rodzaj funkcji jądra i stopień regularyzacji.

Odpowiednie dostosowanie parametrów algorytmu do danego problemu może znacząco poprawić jakość jego działania.

Sklearn posiada zaimplementowanych kilka strategii poszukiwania najlepszych parametrów. Użyjemy tutaj najprostszego podejścia losowego:

from sklearn.model_selection import RandomizedSearchCV

X, y = load_wine(return_X_y=True)

param_distributions = {'svc__kernel': ['linear', 'poly', 'rbf', 'sigmoid'],
                       'svc__C': [0.1, 0.5, 1, 2, 4]}

# now create a searchCV object and fit it to the data
search = RandomizedSearchCV(estimator=pipeline_classifier,
                            n_iter=5,
                            param_distributions=param_distributions)
search.fit(X, y)

print(search.best_params_)
{'svc__kernel': 'rbf', 'svc__C': 2}

Mechanizm wykonuje ilosc powtórzeń zadaną przez argument n_iter i losuje za każdym razem wartości parametrów spośród podanych. Dla każdego z nich wykonuje cross-walidację. Możemy też przeanalizować dokładne wyniki tego procesu:

search.cv_results_
{'mean_fit_time': array([0.00118608, 0.00138006, 0.00139723, 0.00184822, 0.00112281]),
 'std_fit_time': array([4.87129242e-05, 3.16407954e-05, 4.24643770e-05, 7.40724741e-05,
        2.55446788e-05]),
 'mean_score_time': array([0.00036101, 0.00053387, 0.0004117 , 0.00077167, 0.00036922]),
 'std_score_time': array([1.47429845e-05, 7.38158706e-06, 2.64979057e-05, 3.34815074e-05,
        2.99894561e-05]),
 'param_svc__kernel': masked_array(data=['linear', 'rbf', 'poly', 'rbf', 'linear'],
              mask=[False, False, False, False, False],
        fill_value='?',
             dtype=object),
 'param_svc__C': masked_array(data=[2, 2, 0.5, 0.1, 0.1],
              mask=[False, False, False, False, False],
        fill_value='?',
             dtype=object),
 'params': [{'svc__kernel': 'linear', 'svc__C': 2},
  {'svc__kernel': 'rbf', 'svc__C': 2},
  {'svc__kernel': 'poly', 'svc__C': 0.5},
  {'svc__kernel': 'rbf', 'svc__C': 0.1},
  {'svc__kernel': 'linear', 'svc__C': 0.1}],
 'split0_test_score': array([0.94444444, 1.        , 0.94444444, 0.97222222, 0.97222222]),
 'split1_test_score': array([0.97222222, 0.97222222, 0.91666667, 0.97222222, 0.94444444]),
 'split2_test_score': array([0.97222222, 0.97222222, 0.86111111, 0.94444444, 0.97222222]),
 'split3_test_score': array([0.97142857, 1.        , 0.85714286, 0.94285714, 1.        ]),
 'split4_test_score': array([0.97142857, 1.        , 0.97142857, 1.        , 0.97142857]),
 'mean_test_score': array([0.96634921, 0.98888889, 0.91015873, 0.96634921, 0.97206349]),
 'std_test_score': array([0.01095813, 0.01360828, 0.04514025, 0.02113318, 0.01757108]),
 'rank_test_score': array([3, 1, 5, 3, 2], dtype=int32)}

Parametry modelu sprawdzić możemy przy pomocy metody get_params() lub w dokumentacji

pipeline_classifier.get_params()
{'memory': None,
 'steps': [('standardscaler', StandardScaler()), ('svc', SVC())],
 'verbose': False,
 'standardscaler': StandardScaler(),
 'svc': SVC(),
 'standardscaler__copy': True,
 'standardscaler__with_mean': True,
 'standardscaler__with_std': True,
 'svc__C': 1.0,
 'svc__break_ties': False,
 'svc__cache_size': 200,
 'svc__class_weight': None,
 'svc__coef0': 0.0,
 'svc__decision_function_shape': 'ovr',
 'svc__degree': 3,
 'svc__gamma': 'scale',
 'svc__kernel': 'rbf',
 'svc__max_iter': -1,
 'svc__probability': False,
 'svc__random_state': None,
 'svc__shrinking': True,
 'svc__tol': 0.001,
 'svc__verbose': False}

See also

Istnieje szeroki wachlarz metod do selekcji parametrów modelu, poza tymi zaimplementowanymi w sklearnie. Warto przypatrzeć się Hyperopt, HpBandSter lub Optuna.

Ciekawym rozwiązaniem jest też AutoML, automatycznie dobierajacy model i parametry do zadanego problemu - np. rozwiązanie H2O AutoML